import http from 'http'
import https from 'https'
import type { RequestOptions } from 'node:https'
import { buildFullPath, jsonStringify, merge, transformData, transformGetUrl } from './util'
export type RequestConfig = {
    baseUrl?: string
    url?: string
    responseType?: 'arraybuffer' | 'document' | 'json' | 'text' | 'stream'
    timeout?: RequestOptions['timeout']
    headers?: RequestOptions['headers']
    method?: RequestOptions['method']
    params?: Record<string, any>
    data?: any
}
export type ResponseResult = {
    url: string
    protocol: string
    origin: string
    host: string
    pathname: string
    headers: RequestOptions['headers']
    method: RequestOptions['method']
    statusCode: number
    statusText: string
    data: any
}

export type HttpRequestInterceptorsItem = {
    onFulfilled: (config: RequestConfig) => Promise<RequestConfig>
    onRejected: (error: any) => any | Promise<any>
}
export type HttpResponseInterceptorsItem = {
    onFulfilled: (response: ResponseResult) => Promise<any>
    onRejected: (error: any) => any | Promise<any>
}

export type HttpManagerInterceptors = {
    request: HttpRequestInterceptorsItem[]
    response: HttpResponseInterceptorsItem[]
}

export class HttpRequest {
    private interceptors: HttpManagerInterceptors = {
        request: [],
        response: []
    }
    options: RequestConfig
    constructor(options?: RequestConfig) {
        this.options = options || {}
    }
    init(config: RequestConfig) {
        this.options = config || {}
    }

    request(config: RequestConfig): Promise<any> {
        const requestInterceptorChain: any[] = []
        const responseInterceptorChain: any[] = []
        if (Array.isArray(this.interceptors.request) && this.interceptors.request.length) {
            this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
                requestInterceptorChain.unshift(interceptor.onFulfilled, interceptor.onRejected)
            })
        }
        if (Array.isArray(this.interceptors.response) && this.interceptors.response.length) {
            this.interceptors.response.forEach(function unshiftRequestInterceptors(interceptor) {
                responseInterceptorChain.unshift(interceptor.onFulfilled, interceptor.onRejected)
            })
        }

        const chain: any[] = [this.dispatchRequest, undefined]
        Array.prototype.unshift.apply(chain, requestInterceptorChain)
        Array.prototype.push.apply(chain, responseInterceptorChain)
        const len = chain.length
        let promise = Promise.resolve(merge(this.options, config))
        let i = 0
        while (i < len) {
            promise = promise.then(chain[i++], chain[i++])
        }
        return promise
    }

    dispatchRequest(config: RequestConfig): Promise<ResponseResult> {
        return new Promise<ResponseResult>((resolve, reject) => {
            const {
                baseUrl,
                url = '',
                responseType = 'json',
                params = {},
                data,
                method,
                ...options
            } = { ...config } as RequestConfig
            const parsedUrl = new URL(buildFullPath(url, baseUrl))
            const isHttps = parsedUrl.protocol === 'https:' ? true : false
            const httpRequest = isHttps ? https : http

            const request = httpRequest.request(
                method === 'GET' ? transformGetUrl(url, params) : url,
                options,
                (response) => {
                    const { headers, statusCode = 404, statusMessage = '' } = response

                    let responseData = ''
                    response.setEncoding('utf8')
                    response.on('data', (chunk) => {
                        responseData += chunk
                    })
                    response.on('end', () => {
                        resolve({
                            headers,
                            url: parsedUrl.href,
                            protocol: parsedUrl.protocol,
                            origin: parsedUrl.origin,
                            pathname: parsedUrl.pathname,
                            host: parsedUrl.host,
                            method,
                            statusCode,
                            statusText: statusMessage,
                            data: transformData(responseData, responseType)
                        })
                    })
                }
            )
            request.on('error', (error) => {
                reject(error)
            })
            if (method === 'POST' && data) {
                request.write(jsonStringify(data))
            }
            request.end()
        })
    }

    useRequestInterceptor(
        onFulfilled: (config: RequestConfig) => Promise<RequestConfig>,
        onRejected: (error: any) => Promise<any>
    ) {
        this.interceptors.request.push({ onFulfilled, onRejected })
        return () => {
            const index = this.interceptors.request.findIndex(
                (interceptor) =>
                    interceptor.onFulfilled === onFulfilled && interceptor.onRejected === onRejected
            )
            if (index !== -1) {
                this.interceptors.request.splice(index, 1)
            }
        }
    }
    useResponseInterceptor(
        onFulfilled: (response: ResponseResult) => Promise<any>,
        onRejected: (error: any) => Promise<any>
    ) {
        this.interceptors.response.push({ onFulfilled, onRejected })
        return () => {
            const index = this.interceptors.response.findIndex(
                (interceptor) =>
                    interceptor.onFulfilled === onFulfilled && interceptor.onRejected === onRejected
            )
            if (index !== -1) {
                this.interceptors.response.splice(index, 1)
            }
        }
    }
}
const createRequest = (function () {
    let instance: HttpRequest | null = null
    return function () {
        if (!instance) {
            instance = new HttpRequest()
        }
        return instance
    }
})()

export default createRequest
